﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace DynamicGeometry
{
    public abstract class ValueDiscoveryStrategy
    {
        public virtual IEnumerable<IValueProvider> GetValues(object editableObject)
        {
            var properties = GetProperties(editableObject);
            properties = FilterProperties(properties);
            var result = CreateValueProviders(properties, editableObject);
            if (editableObject is IEnumerable)
            {
                int take = 10;
                var items = new List<IValueProvider>();
                foreach (var item in (IEnumerable)editableObject)
                {
                    if (take-- <= 0)
                    {
                        break;
                    }
                    items.Add(new ObjectValue(item));
                }
                result = result.Concat(items);
            }
            return result;
        }

        public BindingFlags? BindingFlags { get; set; }

        protected virtual IEnumerable<PropertyInfo> GetProperties(object editableObject)
        {
            var type = editableObject.GetType();
            PropertyInfo[] properties;
            if (BindingFlags != null)
            {
                properties = type.GetProperties(BindingFlags.Value);
            }
            else
            {
                properties = type.GetProperties();
            }
            return properties
                .Where(p => p.GetIndexParameters().IsEmpty())
                //.OrderBy(p => p.Name)
                ;
        }

        protected virtual IEnumerable<PropertyInfo> FilterProperties(IEnumerable<PropertyInfo> properties)
        {
            return properties;
        }

        protected virtual IEnumerable<IValueProvider> CreateValueProviders(IEnumerable<PropertyInfo> properties, object editableObject)
        {
            foreach (var p in properties)
            {
                IValueProvider result = null;
                if (p.PropertyType.HasInterface<IValueProvider>() && (result = (IValueProvider)p.GetValue(editableObject, null)) != null)
                {
                    yield return result;
                }
                else
                {
                    yield return new PropertyValue(p, editableObject);
                }
            }
        }
    }

    public class IncludeByDefaultValueDiscoveryStrategy : ValueDiscoveryStrategy
    {
        public static IncludeByDefaultValueDiscoveryStrategy Instance = new IncludeByDefaultValueDiscoveryStrategy();

        protected override IEnumerable<PropertyInfo> FilterProperties(IEnumerable<PropertyInfo> properties)
        {
            return properties
                .Where(p => !p.HasAttribute<IgnoreAttribute>() && 
                    (!p.HasAttribute<PropertyGridVisibleAttribute>() 
                    || p.GetAttribute<PropertyGridVisibleAttribute>().Visible));
        }
    }

    public class ExcludeByDefaultValueDiscoveryStrategy : ValueDiscoveryStrategy
    {
        public static ExcludeByDefaultValueDiscoveryStrategy Instance = new ExcludeByDefaultValueDiscoveryStrategy();

        protected override IEnumerable<PropertyInfo> FilterProperties(IEnumerable<PropertyInfo> properties)
        {
            return properties
                .Where(p => 
                    !p.HasAttribute<IgnoreAttribute>() &&
                    (p.HasAttribute<PropertyGridVisibleAttribute>() 
                     && p.GetAttribute<PropertyGridVisibleAttribute>().Visible));
        }
    }

    public class ParameterDiscoveryStrategy : ValueDiscoveryStrategy
    {
        public override IEnumerable<IValueProvider> GetValues(object editableObject)
        {
            var method = editableObject as MethodInfo;
            if (method == null)
            {
                return Enumerable.Empty<IValueProvider>();
            }

            var parameters = method.GetParameters();
            var valueProviders = parameters.Select(p => new ValueProvider()
            {
                Type = p.ParameterType,
                Name = p.Name,
                Parent = editableObject,
                DisplayName = p.Name,
                Value = CreateDefaultValue(p)
            });
            return valueProviders.ToArray();
        }

        static object CreateDefaultValue(ParameterInfo p)
        {
            object result = null;
            try
            {
                result = Activator.CreateInstance(p.ParameterType);
            }
            catch (Exception)
            {
            }
            return result;
        }
    }
}